package im.composer.media.sound.codec.seek;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.nio.file.Path;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import org.tritonus.share.sampled.FloatInputStream;

public abstract class AbstractSeekSupport {

	private static final ExecutorService es = Executors.newCachedThreadPool(new ThreadFactory() {
		
		@Override
		public Thread newThread(Runnable r) {
			Thread t = new Thread(r);
			t.setDaemon(true);
			t.setPriority(Thread.MIN_PRIORITY);
			return t;
		}
	});

	private static SeekMarkStorage STORAGE = null;

	public enum SeekSupportState {
		UNINITED, SUPPORT, UNSUPPORTED, BUILDING
	}

	protected final Path path;
	protected FloatInputStream fis;
	protected NavigableMap<Long, Long> pos_map = new TreeMap<>();
	protected SeekSupportState state = SeekSupportState.UNINITED;

	public AbstractSeekSupport(Path path) {
		this.path = path;
		if (STORAGE == null) {
			state = SeekSupportState.UNSUPPORTED;
			return;
		}
		if (STORAGE.seekMarkExists(path)) {
			try {
				readSeekIndex();
			} catch (Exception e) {
			}
		}
		if (pos_map.isEmpty()) {
			state = SeekSupportState.BUILDING;
			es.submit(buildWorkder());
		} else {
			state = SeekSupportState.SUPPORT;
		}
	}

	public abstract void seek(long sample_position);

	protected abstract Callable<Void> buildWorkder();

	protected void readSeekIndex() throws Exception {
		pos_map.clear();
		DataInputStream in = new DataInputStream(STORAGE.getInputStreamForSeekMark(path));
		while (in.available() > 0) {
			long key = in.readLong();
			long val = in.readLong();
			pos_map.put(key, val);
		}
		in.close();
	}

	protected void writeSeekIndex() throws Exception {
		DataOutputStream out = new DataOutputStream(STORAGE.getOutputStreamForSeekMark(path));
		for (Entry<Long, Long> entry : pos_map.entrySet()) {
			out.writeLong(entry.getKey());
			out.writeLong(entry.getValue());
		}
		out.flush();
		out.close();
	}

	public synchronized long maxIndex() {
		while (state == SeekSupportState.BUILDING) {
			try {
				Thread.sleep(0);
			} catch (InterruptedException e) {
			}
		}
		return pos_map.isEmpty() ? -1 : pos_map.lastKey();
	}

	public void setOutput(FloatInputStream fis) {
		this.fis = fis;
	}

	public boolean isEnabled() {
		return state == SeekSupportState.BUILDING || state == SeekSupportState.SUPPORT;
	}

	public static void setStorage(SeekMarkStorage storage) {
		STORAGE = storage;
	}
}
